﻿/*
 * Copyright (c) 2009, Thiago M. Sayao <thiago.sayao@gmail.com>
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *  * Redistributions of source code must retain the above copyright notice, 
 *    this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  * Neither the name of the author nor the names of its contributors may be 
 *    used to endorse or promote products derived from this software without 
 *    specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Security;
using System.Linq;
using System.IO;
using System.Text;
using System.Net.Mail;
using DotNetBurner.Model;
using DotNetBurner.Core;
using DotNetBurner.Web;
using DotNetBurner.Web.Settings;
using Resources;

namespace DotNetBurner
{
    [DotNetBurner.Web.HandleError]
    public class StoryController : BaseController
    {


        [AcceptVerbs (HttpVerbs.Get | HttpVerbs.Head), CompressFilter, OutputCache (CacheProfile = "Feeds")]
        public ActionResult UpcomingFeed(string sort, string feedType, int? feedCount)
        {
            return Content(Feed("Upcoming", null, sort, feedType, feedCount), "application/xml", Encoding.UTF8);
        }

        [AcceptVerbs (HttpVerbs.Get | HttpVerbs.Head), CompressFilter, OutputCache (CacheProfile = "Feeds")]
        public ActionResult IndexFeed (string sort, string feedType, int? feedCount)
        {
            return Content(Feed("Index", null, sort, feedType, feedCount), "application/xml", Encoding.UTF8);
        }

        [AcceptVerbs (HttpVerbs.Get | HttpVerbs.Head), CompressFilter, OutputCache (CacheProfile = "Feeds")]
        public ActionResult CategoryFeed (int? id, string sort, string feedType, int? feedCount)
        {
            return Content(Feed("Category", id, sort, feedType, feedCount), "application/xml", Encoding.UTF8);
        }

        [AcceptVerbs (HttpVerbs.Get | HttpVerbs.Head), CompressFilter, OutputCache (CacheProfile = "Feeds")]
        public ActionResult TagFeed (int? id, string sort, string feedType, int? feedCount)
        {
            return Content(Feed("Tag", id, sort, feedType, feedCount), "application/xml", Encoding.UTF8);
        }

        [AcceptVerbs (HttpVerbs.Get | HttpVerbs.Head), CompressFilter, OutputCache (CacheProfile = "Feeds")]
        public ActionResult PostedByFeed (int? id, string sort, string feedType, int? feedCount)
        {
            return Content(Feed("PostedBy", id, sort, feedType, feedCount), "application/xml", Encoding.UTF8);
        }

        string Feed(string feed, int? id, string sort, string feedType, int? feedCount)
        {
            int sortDays = GetSortDays(sort);

            if (!id.HasValue)
                id = 0;

            if (!feedCount.HasValue || feedCount == 0)
                feedCount = StorySettings.Settings.StoryPerFeed;

            if (feedCount > 200)
                feedCount = 200;

            IEnumerable<Story> stories = null;

            var categories = Repository.GetCategories();

            switch (feed)
            {
                case "Index":
                    stories = Repository.GetPublishedStoriesForAllCategory(CurrentUserId, sortDays, 1, (int)feedCount);
                    break;

                case "Category":
                    stories = Repository.GetPublishedStoriesForCategory(CurrentUserId, id.Value, sortDays, 1, (int)feedCount);
                    break;

                case "Upcoming":
                    stories = Repository.GetUpcomingStories (CurrentUserId, sortDays, 1, (int)feedCount);
                    break;

                case "Tag":
                    stories = Repository.GetStoriesForTag(CurrentUserId, id.Value, sortDays, 1, (int)feedCount);
                    break;

                case "PostedBy":
                    stories = Repository.GetStoriesPostedByUser(CurrentUserId, id.Value, sortDays, 1, (int)feedCount);
                    break;

                default:
                    break;
            }

            FeedType type = (feedType.Equals("RSS", StringComparison.InvariantCultureIgnoreCase)) 
                            ? FeedType.RSS20 
                            : FeedType.ATOM10;

            using (var memoryStream = new MemoryStream ())
            {
                FeedHelper.GenerateFeed (stories, categories, memoryStream, type, Url);
                return Encoding.UTF8.GetString (memoryStream.ToArray ());
            }
        }

        [AcceptVerbs (HttpVerbs.Get | HttpVerbs.Head), CompressFilter]
        public ActionResult Upcoming (int? page, string sort)
        {
            var viewData = GetStoryListViewData<StoryListByCategoryData> (page);
            int sortDays = GetSortDays (sort);

            viewData.Stories = Repository.GetUpcomingStories (CurrentUserId, sortDays, CalculateStartIndex(page), 
                                                              StorySettings.Settings.StoryPerPage);

            viewData.Category = Strings.Upcoming;
            viewData.CategoryDisplayName = Strings.Upcoming;
            viewData.SortDays = sortDays;

            return View ("Upcoming", viewData);
        }

        [AcceptVerbs (HttpVerbs.Get | HttpVerbs.Head), CompressFilter]
        public ActionResult Tag (int id, int? page, string sort)
        {
            var viewData = GetStoryListViewData<StoryListByTagData>(page);
            string name = String.Empty;

            Tag tag = Repository.GetTagById(id);
            int sortDays = GetSortDays(sort);

            if (tag != null)
            {
                name = tag.Name;
                viewData.Stories = Repository.GetStoriesForTag(CurrentUserId, tag.TagId, sortDays,
                                                               CalculateStartIndex(page),
                                                               StorySettings.Settings.StoryPerPage);
            }

            viewData.Tag = name;
            viewData.SortDays = sortDays;

            return View("Tag", viewData);
        }

        [AcceptVerbs (HttpVerbs.Get | HttpVerbs.Head), CompressFilter]
        public ActionResult PostedBy (int id, int? page, string sort)
        {
            var viewData = GetStoryListViewData<StoryListByUserData>(page);
            int sortDays = GetSortDays(sort);
            string name = String.Empty;

            object providerKey = Repository.GetProviderUserKey(id);
            MembershipUser user = UserManager.GetUser(providerKey, false);

            if (user != null)
            {
                name = user.UserName;

                viewData.Stories = Repository.GetStoriesPostedByUser(CurrentUserId, id, sortDays,
                                                                     CalculateStartIndex(page),
                                                                     StorySettings.Settings.StoryPerPage);
            }

            viewData.PostedBy = name;
            viewData.SortDays = sortDays;

            return View("PostedBy", viewData);
        }

        [AcceptVerbs (HttpVerbs.Get | HttpVerbs.Head), CompressFilter]
        public ActionResult Search(string q, int? page)
        {
            if (string.IsNullOrEmpty(q))
            {
                return Redirect(Request.UrlReferrer.AbsoluteUri);
            }

            q = Server.HtmlDecode(q);

            var viewData = GetStoryListViewData<StoryListBySearchData>(page);
            viewData.Stories = Repository.SearchStories(CurrentUserId, q, CalculateStartIndex(page),
                                                        StorySettings.Settings.StoryPerPage);
            viewData.SearchQuery = q;

            return View("Search", viewData);
        }

        [AcceptVerbs (HttpVerbs.Get | HttpVerbs.Head), CompressFilter]
        public ActionResult LanguageDetail (int id, string title)
        {
            return RedirectToRoute("Detail", new RouteValueDictionary{{"id", id}, {"title", title}});
        }

        [AcceptVerbs (HttpVerbs.Get | HttpVerbs.Head), CompressFilter]
        public ActionResult Detail(int id, string title)
        {
            StoryDetail story = Repository.GetStoryDetailById (CurrentUserId, id);

            if (story != null)
            {
                var viewData = GetViewData<StoryDetailData> ();
                viewData.Story = story;

                //if the user can edit the story...
                viewData.UserCanEditStory = false;
                viewData.UserIsAdm = IsUserAdm;
                viewData.CategoryName = story.CategoryName;

                if (IsUserAdm
                    || IsUserModerator
                    || (viewData.Story.PostedByUser.UserId == CurrentUserId && !viewData.Story.IsPublished))
                {
                    viewData.UserCanEditStory = true;
                }

                viewData.RelatedStories = Repository.GetRelatedStories(story.StoryId,
                                                                       StorySettings.Settings.RelatedStories);

                //FIXME: configuration parameter
                viewData.TopInCategory = Repository.GetTopStoriesInCategory(story.CategoryId,
                                                                            StorySettings.Settings.RelatedStories);

                return View ("Detail", viewData);
            }

            return View ("NotFound", GetViewData<BaseViewData> ());
        }

        [AcceptVerbs (HttpVerbs.Get | HttpVerbs.Head), Authorize, CompressFilter]
        public ActionResult Edit(int id)
        {
            Story story = Repository.GetStoryDetailById(CurrentUserId, id);

            var viewData = GetViewData<StorySubmitData>();
            viewData.StoryTitle = story.Title;
            viewData.StoryUrl = story.Url;
            viewData.StoryDescription = story.Description;
            viewData.StoryTags = String.Join(", ", (from t in story.Tags select t.Name).ToArray());
            viewData.StoryCategoryId = story.CategoryId;
            viewData.StoryId = story.StoryId;
            viewData.AutoCompleteData = GetSubmitAutoCompleteTags();

            return View("Submit", viewData);
        }

        [AcceptVerbs(HttpVerbs.Post), Authorize, ValidateAntiForgeryToken(Salt = "delete"), CompressFilter] 
        public JsonResult Delete(int storyId, int userId, string reason)
        {
            List<string> errors = new List<string>();

            if (IsUserAdm)
            {
                object providerKey = Repository.GetProviderUserKey(userId);
                string storyTitle = Repository.GetStoryTitle(storyId);

                if (providerKey != null)
                {
                    Repository.StoryDelete(storyId);

                    try
                    {
                        SendStoryDeleteMail(reason, CurrentUserName, storyTitle,
                                            UserManager.GetUser(providerKey, false).Email);
                    }
                    catch (SmtpException exception)
                    {
                        errors.Add(Strings.FailedToSendEmailNotification);
                        Log.Exception(exception);
                    }
                }
            }

            var result = new JsonResult();

            result.Data = new
            {
                errors,
                hasErrors = !(errors.Count == 0)
            };

            return Json(result);
        }

        [AcceptVerbs (HttpVerbs.Get | HttpVerbs.Head), Authorize, ValidateInput (false), CompressFilter]
        public ActionResult Submit (int? id, string url, string title, string desc, string tags, string category)
        {
            var viewData = GetViewData<StorySubmitData> ();

            if (!String.IsNullOrEmpty (url))
                viewData.StoryUrl = url;
            
            if (!String.IsNullOrEmpty(title))
                viewData.StoryTitle = title; 

            if (!String.IsNullOrEmpty (desc))
                viewData.StoryDescription = desc; 

            if (!String.IsNullOrEmpty (tags))
                viewData.StoryTags = tags; 

            if (!String.IsNullOrEmpty (category))
            {
                Category cat = Repository.GetCategoryByName (category);

                if (cat != null)
                    viewData.StoryCategoryId = cat.CategoryId;
            }

            viewData.AutoCompleteData = GetSubmitAutoCompleteTags();

            return View ("Submit", viewData);
        }

        [AcceptVerbs (HttpVerbs.Post), ValidateAntiForgeryToken (Salt = "submit"), ValidateInput(false), CompressFilter]
        public JsonResult Submit(int? storyId, string storyUrl, string storyTitle, short storyCategoryId, string storyDescription, string storyTags)
        {
            List<string> errors = new List<string>();

            if (!IsUserAuthenticated)
                errors.Add(Strings.NotAuthenticated);

            //url validation
            if (string.IsNullOrEmpty(storyUrl))
                errors.Add(Strings.UrlCannotBeBlank);
            else if (storyUrl.Length > StorySettings.Settings.MaxUrlSizeInChars)
                errors.Add (String.Format (Strings.UrlTooBig, StorySettings.Settings.MaxUrlSizeInChars));
            else if (!storyUrl.IsValidUrl())
                errors.Add(Strings.InvalidUrl);

            //story title validation
            if (string.IsNullOrEmpty(storyTitle))
                errors.Add(Strings.TitleCannotBeBlank);
            else if (storyTitle.Length > StorySettings.Settings.MaxTitleSizeInChars)
                errors.Add(String.Format(Strings.TitleTooBig, StorySettings.Settings.MaxTitleSizeInChars));
            else if (storyTitle.Contains(' ') && storyTitle.ToUpperInvariant() == storyTitle)
                errors.Add(Strings.TitleAllCaps);
            else if (storyTitle.Contains(' ') && storyTitle.ToLowerInvariant() == storyTitle)
                errors.Add(Strings.TitleAllLower);

            //desc cannot be blank
            if (string.IsNullOrEmpty(storyDescription.Trim()))
                errors.Add(Strings.DescCannotBeBlank);
            //max desc size
            else if (storyDescription.Length > StorySettings.Settings.MaxStoryDescSizeInChars)
                errors.Add (Strings.DescTooBig.FormatWith(StorySettings.Settings.MaxStoryDescSizeInChars));
            //min desc size
            else if (storyDescription.Length < StorySettings.Settings.MinStoryDescSizeInChars)
                errors.Add(Strings.DescTooSmall.FormatWith(StorySettings.Settings.MinStoryDescSizeInChars));
            else if (storyDescription.Trim().IndexOf(' ') == -1)
                errors.Add(Strings.DescMustHaveSpaces);

            //category
            if (storyCategoryId <= 0)
                errors.Add(Strings.PleaseSelectCategory);

            //tag validation
            if (!String.IsNullOrEmpty(storyTags))
            {
                storyTags = storyTags.Trim(new[] { ',', ' ' });

                string[] tags = storyTags.Trim().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

                //max tag size
                if (!tags.All (f => f.Trim ().Length <= StorySettings.Settings.MaxTagSizeInChars))
                    errors.Add (String.Format (Strings.TagTooBig, StorySettings.Settings.MaxTagSizeInChars));

                //min tag size
                if (!tags.All (f => f.Trim ().Length >= StorySettings.Settings.MinTagSizeInChars || f.Trim ().Length == 0))
                    errors.Add (String.Format (Strings.TagTooSmall, StorySettings.Settings.MinTagSizeInChars));

                //max tag per story
                if (tags.Length > StorySettings.Settings.MaxTagsPerStory)
                    errors.Add (String.Format (Strings.TooManyTags, StorySettings.Settings.MaxTagsPerStory));

                //chars allowed
                if (!tags.All(f => f.Trim().IsValidTag()))
                    errors.Add(Strings.CharsAllowedForTags);
            }

            int id = -1;

            if (errors.Count == 0)
            {
                try
                {
                    bool triggerExternalServices = false;

                    if (storyId.HasValue && storyId > 0)
                    {
                        id = storyId.Value;

                        //update
                        Story story = Repository.GetStoryDetailById(CurrentUserId, id);

                        if (story.PostedByUser.UserId != CurrentUserId && !IsUserAdmOrModerator)
                            errors.Add(Strings.StoryCanOnlyBeEditedBy);

                        if (!IsUserAdmOrModerator && story.IsPublished)
                            errors.Add(Strings.PublishedStoriesCannotBeEdited);

                        if (errors.Count == 0)
                        {
                            Repository.UpdateStory(id, storyUrl, storyTitle, storyCategoryId, storyDescription,
                                                   storyTags);
                        }

                        if (story.Url != storyUrl)
                        {
                            //if URL changes, trigger external services
                            triggerExternalServices = true;
                        }
                    }
                    else
                    {
                        if (Repository.GetStoryByUrl(storyUrl) != -1)
                        {
                            errors.Add(Strings.StoryExists);
                        }
                        else
                        {
                            //insert
                            id = Repository.SubmitStory(storyUrl, storyTitle, storyCategoryId, storyDescription, storyTags,
                                                        CurrentUserId);
                            
                            triggerExternalServices = true;
                        }
                    }


#if DEBUG
                    //when debugging, external services is off
                    triggerExternalServices = false;
#endif

                    if (triggerExternalServices)
                    {
                        try
                        {
                            string url = SiteSettings.Settings.Address;
                            url += Url.RouteUrl("Detail", new RouteValueDictionary 
                                                              {
                                                                    { "id", id }, 
                                                                    { "title", storyTitle.ConvertToUrlPath()}
                                                              });

                            string category = Repository.GetCategoryById(storyCategoryId).Name;

                            //send trackback
                            if (DotNetBurnerSettings.Settings.TrackbackEnabled)
                                Trackback.Send(storyUrl, storyTitle, url, DotNetBurnerSettings.Settings.TrackBackMessage,
                                               SiteSettings.Settings.Name, category);


                            //request pageglimpse
                            if (PageGlimpseSettings.Settings.Enabled)
                                PageGlimpse.RequestSiteThumbnail(storyUrl, PageGlimpseSettings.Settings.DevKey);
                        }
                        catch (Exception exception)
                        {
                            //this exception is not rethrown because we dont care if this fails
                            Log.Exception(exception);
                        }
                    }
                }
                catch (Exception exception)
                {
                    errors.Add(Strings.FailedToCreateStory);
                    Log.Exception(exception);
                }
            }

            var result = new JsonResult();

            result.Data = new
            {
                errors,
                hasErrors = !(errors.Count == 0),
                url = Url.RouteUrl("Detail", new RouteValueDictionary{{"id", id}, {"title", storyTitle.ConvertToUrlPath ()}})
            };

            return Json(result);
        }

        [AcceptVerbs (HttpVerbs.Post), CompressFilter]
        public JsonResult Vote(int storyId)
        {
            string error = String.Empty;
            string logonUrl = String.Empty; //used if the user is not authenticated

            if (!IsUserAuthenticated)
            {
                string storyUrl = Repository.GetStoryUrlById(storyId);

                string returnUrl = Url.RouteUrl("Vote", new RouteValueDictionary{{"url", storyUrl}});

                logonUrl = Url.RouteUrl("LogOn", new RouteValueDictionary {{"ReturnUrl", returnUrl}});
            }
            else
            {
                try
                {
                    bool published = Repository.VoteInStory(storyId, CurrentUserId,
                                                            StorySettings.Settings.QualifyingVote);

                    if (published)
                        PostOnTwitter(storyId);
                }
                catch (InvalidOperationException e)
                {
                    error = e.Message;
                }
            }

            var result = new JsonResult();
            result.Data = new 
            {
                error,
                logonUrl
            };

            return Json(result);
        }

        [AcceptVerbs (HttpVerbs.Get), Authorize, CompressFilter]
        public ActionResult Vote (string url, string title, string desc, string tags, string category)
        {
            int storyId = Repository.GetStoryByUrl (url);

            //the story does not exist, redirect to submit
            if (storyId == -1)
            {
                //do not use RedirectToRoute here
                return RedirectToAction("Submit", new RouteValueDictionary
                                                     {
                                                         {"url", url}, 
                                                         {"title", title}, 
                                                         {"desc", desc}, 
                                                         {"tags", tags}, 
                                                         {"category", category}
                                                     });
            }

            bool published = Repository.VoteInStory(storyId, CurrentUserId, StorySettings.Settings.QualifyingVote);

            if (published)
                PostOnTwitter(storyId);

            string storyTitle = Repository.GetStoryTitle(storyId);

            return RedirectToRoute("Detail", new RouteValueDictionary
                                                 {
                                                     {"id", storyId},
                                                     {"title", storyTitle.ConvertToUrlPath()}
                                                 });
        }

        [AcceptVerbs(HttpVerbs.Post), CompressFilter]
        public JsonResult Publish(int storyId)
        {
            var errors = new List<string>();

            if (!IsUserAuthenticated)
                errors.Add(Strings.NotAuthenticated);

            if (!IsUserAdmOrModerator)
                errors.Add(Strings.StoryCanOnlyBeEditedBy);

            if (errors.Count == 0)
            {
                Repository.StoryPublish(storyId);
                PostOnTwitter (storyId);
            }

            var res = new JsonResult { Data = new { errors } };

            return Json(res);
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public void Click(int storyId)
        {
            Repository.StoryClick(storyId);
        }

        [AcceptVerbs (HttpVerbs.Get | HttpVerbs.Head), CompressFilter]
        public ActionResult Index(int? page, string sort)
        {
            var viewData = GetStoryListViewData<StoryListByCategoryData>(page);
            int sortDays = GetSortDays(sort);

            viewData.Stories = Repository.GetPublishedStoriesForAllCategory(CurrentUserId, sortDays,
                                                                            CalculateStartIndex(page),
                                                                            StorySettings.Settings.StoryPerPage);
            viewData.Category = String.Empty;
            viewData.CategoryDisplayName = Strings.Popular;
            viewData.SortDays = sortDays;

            return View("Index", viewData);
        }

        [AcceptVerbs (HttpVerbs.Get | HttpVerbs.Head), CompressFilter]
        public ActionResult Category (int? id, int? page, string sort)
        {
            var viewData = GetStoryListViewData<StoryListByCategoryData> (page);
            Category category = null;
            string name = String.Empty;

            if (id.HasValue)
            {
                category = Repository.GetCategoryById (id.Value);
                name = category.Name;
            }
            else
                id = 0;

            int sortDays = GetSortDays(sort);

            if (category != null)
            {
                viewData.Stories = Repository.GetPublishedStoriesForCategory(CurrentUserId, id.Value, sortDays,
                                                                             CalculateStartIndex(page),
                                                                             StorySettings.Settings.StoryPerPage);
            }

            viewData.Category = name;
            viewData.CategoryDisplayName = name;
            viewData.SortDays = sortDays;
            viewData.CategoryId = id.Value;

            return View("Category", viewData);
        }

        [AcceptVerbs (HttpVerbs.Post), ValidateAntiForgeryToken (Salt = "comment"), ValidateInput (false), CompressFilter]
        public JsonResult Comment(int storyId, string commentContent)
        {
            var errors = new List<string>();
            
            if (!IsUserAuthenticated)
                errors.Add(Strings.NotLoggedIn);
            if (string.IsNullOrEmpty(commentContent.Trim()))
                errors.Add(Strings.CommentCannotBeBlank);

            if (errors.Count == 0)
            {
                Repository.PostComment(storyId, CurrentUserId, commentContent);
            }

            var res = new JsonResult { Data = new { errors, hasErrors = (errors.Count != 0) } };

            return Json(res);
        }

        [CompressFilter, OutputCache (CacheProfile = "Integrate")]
        public ActionResult GetVoteCount (string url)
        {
            if (string.IsNullOrEmpty (url))
                return Content (String.Empty);

            int votes = Repository.GetVoteCountByUrl (url);

            return Content (votes.ToString (CultureInfo.InvariantCulture));
        }

        [AcceptVerbs(HttpVerbs.Get | HttpVerbs.Head), CompressFilter]
        public ActionResult Tags(string sort, string q, int? page)
        {
            var viewData = GetViewData<TagListViewData>();

            viewData.CurrentPage = page ?? 1;
            viewData.ItemsPerPage = StorySettings.Settings.TagsPerPage;
            viewData.Sort = sort;
            viewData.Query = q;
            viewData.Tags = Repository.GetAllTags(q, GetTagsSort(sort), 
                                                  TagsCalculateStartIndex(page), 
                                                  viewData.ItemsPerPage);

            return View("Tags", viewData);
        }

        /*
        [AcceptVerbs (HttpVerbs.Get | HttpVerbs.Head), CompressFilter]
        public ActionResult Planet()
        {
            var viewData = GetViewData<PlanetData>();

            PlanetManager pm = new PlanetManager(Repository);
            viewData.Html = pm.GetPlanetHtml();

            return View("Planet", viewData);
        }

        [AcceptVerbs (HttpVerbs.Get | HttpVerbs.Head), CompressFilter]
        public ActionResult PlanetFeed()
        {
            PlanetManager pm = new PlanetManager(Repository);
            string feedContent = pm.GetPlanetFeed();

            return Content(feedContent, "application/xml", Encoding.UTF8);
        }
        */

        void PostOnTwitter (int storyId)
        {
            if (!TwitterSettings.Settings.Enabled)
                return;

            string title = Repository.GetStoryTitle(storyId);

            if (!String.IsNullOrEmpty(title))
            {
                string url = String.Concat(SiteSettings.Settings.Address,
                                           Url.RouteUrl("Detail", new RouteValueDictionary{{"id", storyId}, {"title", title.ConvertToUrlPath ()}}));
                Twitter.Post (title, url);
            }
        }

        static int GetSortDays(string sort)
        {
            if(String.IsNullOrEmpty(sort))
                return 0;

            if (sort.Equals("latest", StringComparison.InvariantCultureIgnoreCase))
                return 0;

            if (sort.Equals("24hours", StringComparison.InvariantCultureIgnoreCase))
                return 1;

            if (sort.Equals("7days", StringComparison.InvariantCultureIgnoreCase))
                return 7;

            if (sort.Equals("1month", StringComparison.InvariantCultureIgnoreCase))
                return 30;

            if (sort.Equals("1year", StringComparison.InvariantCultureIgnoreCase))
                return 365;

            return 0;
        }

        static TagListOrderBy GetTagsSort(string sort)
        {
            if (String.IsNullOrEmpty(sort))
                return TagListOrderBy.Name;

            if (sort.Equals("popular", StringComparison.InvariantCultureIgnoreCase))
                return TagListOrderBy.StoryCount;

            return TagListOrderBy.Name;
        }

        IEnumerable<TopUser> GetTopLeaders()
        {
            return Repository.GetTopLeaders(DotNetBurnerSettings.Settings.MaxTopUsers,
                                            SiteSettings.Settings.AdminRole,
                                            UserPointsSettings.Settings.SubmitStory,
                                            UserPointsSettings.Settings.SubmittedStoryVoted,
                                            UserPointsSettings.Settings.SubmittedStoryPublished,
                                            UserPointsSettings.Settings.SubmittedStoryCommented,
                                            UserPointsSettings.Settings.Comment,
                                            UserPointsSettings.Settings.Vote,
                                            UserPointsSettings.Settings.SubmittedStoryClicked);
        }

        IEnumerable<TopUser> GetTopMovers()
        {
            return Repository.GetTopMovers(DotNetBurnerSettings.Settings.MaxTopUsers,
                                           SiteSettings.Settings.AdminRole,
                                           UserPointsSettings.Settings.SubmitStory,
                                           UserPointsSettings.Settings.SubmittedStoryVoted,
                                           UserPointsSettings.Settings.SubmittedStoryPublished,
                                           UserPointsSettings.Settings.SubmittedStoryCommented,
                                           UserPointsSettings.Settings.Comment,
                                           UserPointsSettings.Settings.Vote,
                                           UserPointsSettings.Settings.SubmittedStoryClicked);
        }

        T GetStoryListViewData<T> (int? page) where T : BaseStoryListData, new ()
        {
            var viewData = GetViewData<T>();
            viewData.CurrentPage = page ?? 1;
            viewData.ItemsPerPage = StorySettings.Settings.StoryPerPage;
            viewData.Tags = new LazyCollection<Tag>(() => Repository.GetTags(StorySettings.Settings.TopTags));
            viewData.TopLeaders = new LazyCollection<TopUser>(GetTopLeaders);
            viewData.TopMovers = new LazyCollection<TopUser>(GetTopMovers);
            viewData.IsUserAdm = IsUserAdm;
            viewData.IsUserModerator = IsUserModerator;

            return viewData;
        }

        static void SendStoryDeleteMail(string reason, string deletedBy, string storyTitle, string email)
        {
            if (String.IsNullOrEmpty(reason))
                return;

            string body = SwissKnife.GetMailTemplateForLang("StoryDelete")
                          .Replace("<%Reason%>", reason)
                          .Replace("<%StoryTitle%>", storyTitle)
                          .Replace("<%DeletedBy%>", deletedBy);

            SendMail(email, SiteSettings.Settings.Name, body);
        }

        string GetSubmitAutoCompleteTags()
        {
            //get the top 300 tags - TODO: maybe make it configurable
            var tags = Repository.GetTags(300);

            var res = from t in tags
                      select t.Name.Replace("\"", "\\\"");

            return String.Join("|", res.ToArray());
        }

        static int BaseCalculateStartIndex(int? page, int itemsPerPage)
        {
            if (page.HasValue && page.Value > 1)
                return ((page.Value - 1) * itemsPerPage) + 1;

            return 1;
        }

        static int CalculateStartIndex(int? page)
        {
            return BaseCalculateStartIndex(page, StorySettings.Settings.StoryPerPage);
        }

        static int TagsCalculateStartIndex(int? page)
        {
            return BaseCalculateStartIndex(page, StorySettings.Settings.TagsPerPage);
        }
    }
}